#ifndef COMMON_FLAGS_H
#define COMMON_FLAGS_H

#include <type_traits>

template <typename Enum>
struct Flags
{
    static_assert(std::is_enum_v<Enum> == true);

    typedef typename std::conditional<
        std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
        unsigned int,
        signed int>::type Int;

    constexpr inline Flags() noexcept
        : m_data(0)
    {
    }

    constexpr inline Flags(Enum flags) noexcept
        : m_data(Int(flags))
    {
    }

    constexpr inline Flags(Int flag) noexcept
        : m_data(flag)
    {
    }

    inline Flags& operator&=(int mask) noexcept
    {
        m_data &= mask;
        return *this;
    }

    inline Flags& operator&=(unsigned mask) noexcept
    {
        m_data &= mask;
        return *this;
    }

    inline Flags& operator&=(Enum mask) noexcept
    {
        m_data &= Int(mask);
        return *this;
    }

    inline Flags& operator|=(Flags other) noexcept
    {
        m_data |= other.m_data;
        return *this;
    }

    inline Flags& operator|=(Enum other) noexcept
    {
        m_data |= Int(other);
        return *this;
    }

    inline Flags& operator^=(Flags other) noexcept
    {
        m_data ^= other.m_data;
        return *this;
    }

    inline Flags& operator^=(Enum other) noexcept
    {
        m_data ^= Int(other);
        return *this;
    }

    inline operator Int() const noexcept
    {
        return m_data;
    }

    inline bool operator!() const noexcept
    {
        return !m_data;
    }

    inline bool testFlag(Enum flag) const noexcept
    {
        return bool(
            (m_data & Int(flag)) == Int(flag)
            && (Int(flag) != 0 || m_data == Int(flag))
        );
    }

    inline Flags& setFlag(Enum flag, bool on = true) noexcept
    {
        return on ? (*this |= flag) : (*this &= ~Int(flag));
    }

private:
    Int m_data;
};

#endif // COMMON_FLAGS_H
